"""
differences between shallow copy and deep copy
1.shallow copy:  constructing a new collection object and then
populating it with references to the child objects found in the original.
therefore them won’t create copies of the child objects themselves.
2.deep copy:  makes the copying process recursive.It means first constructing
a new collection object and then recursively populating it with copies of the
child objects found in the original.
"""
import copy

# making shallow copies
x = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# two different ways to make shallow copy
y_factory_copy = list(x)
y_copy = copy.copy(x)
print(x)
print(y_factory_copy)
print(y_copy)
x.append([0, 0, 0])
print(x)
# in shallow copy, y still contains references to the original child objects stored in x
print(y_factory_copy)
print(y_copy)
# children were not copied. They were merely referenced again in the copied list
x[1][0] = 'Super'
print(x)
print(y_factory_copy)
print(y_copy)

# making deep copies
xs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
zs = copy.deepcopy(xs)
print(xs)
print(zs)
xs[1][0] = 'Super'
print(xs)
# this modification won’t affect the deep copy (zs) because it is fully independent
print(zs)


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'Point({self.x!r}, {self.y!r})'


class rect:
    def __init__(self, left_top, right_bottom):
        self.left_top = left_top
        self.right_bottom = right_bottom

    def __repr__(self):
        return f'Rectangle({self.left_top!r}, {self.right_bottom!r})'


if __name__ == '__main__':
    a = Point(10, 3)
    print('............Create arbitrary objects............')
    print(a)
    b = copy.copy(a)
    c = copy.deepcopy(a)
    # shallow copy --> 'a is b' results False
    print(a is b)
    print(a is c)
    d = Point(5, 12)
    rect_a = rect(d, a)
    print(rect_a)
    # different copies of rect_a
    shallow_rect_a = copy.copy(rect_a)
    deep_rect_a = copy.deepcopy(rect_a)
    print('shallow copy of rect_a: {}'.format(shallow_rect_a))
    print('deep copy of rect_a: {}'.format(deep_rect_a))
    rect_a.left_top.x = 100
    # after modifying
    print('shallow copy of rect_a: {}'.format(shallow_rect_a))
    print('deep copy of rect_a: {}'.format(deep_rect_a))
